{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 4,
   "source": [
    "\"\"\"_summary_\n",
    "RANSAC直线拟合\n",
    "\"\"\"\n",
    "from copy import copy\n",
    "import numpy as np\n",
    "from numpy.random import default_rng\n",
    "rng = default_rng()\n",
    "\n",
    "class RANSAC:\n",
    "    def __init__(self, n=10, k=100, t=0.05, d=10, model=None, loss=None, metric=None):\n",
    "        self.n = n              \n",
    "        self.k = k              \n",
    "        self.t = t              \n",
    "        self.d = d              \n",
    "        self.model = model     \n",
    "        self.loss = loss        \n",
    "        self.metric = metric  \n",
    "        self.best_fit = None\n",
    "        self.best_error = np.inf\n",
    "\n",
    "    def fit(self, X, y):\n",
    "\n",
    "        for _ in range(self.k):\n",
    "            ids = rng.permutation(X.shape[0])\n",
    "\n",
    "            maybe_inliers = ids[: self.n]\n",
    "            maybe_model = copy(self.model).fit(X[maybe_inliers], y[maybe_inliers])\n",
    "\n",
    "            thresholded = (\n",
    "                self.loss(y[ids][self.n :], maybe_model.predict(X[ids][self.n :]))\n",
    "                < self.t\n",
    "            )\n",
    "\n",
    "            inlier_ids = ids[self.n :][np.flatnonzero(thresholded).flatten()]\n",
    "\n",
    "            if inlier_ids.size > self.d:\n",
    "                inlier_points = np.hstack([maybe_inliers, inlier_ids])\n",
    "                better_model = copy(self.model).fit(X[inlier_points], y[inlier_points])\n",
    "\n",
    "                this_error = self.metric(\n",
    "                    y[inlier_points], better_model.predict(X[inlier_points])\n",
    "                )\n",
    "\n",
    "                if this_error < self.best_error:\n",
    "                    self.best_error = this_error\n",
    "                    self.best_fit = maybe_model\n",
    "\n",
    "        return self\n",
    "\n",
    "    def predict(self, X):\n",
    "        return self.best_fit.predict(X)\n",
    "\n",
    "\n",
    "def square_error_loss(y_true, y_pred):\n",
    "    return (y_true - y_pred) ** 2\n",
    "\n",
    "def mean_square_error(y_true, y_pred):\n",
    "    return np.sum(square_error_loss(y_true, y_pred)) / y_true.shape[0]\n",
    "\n",
    "class LinearRegressor:\n",
    "    def __init__(self):\n",
    "        self.params = None\n",
    "\n",
    "    def fit(self, X: np.ndarray, y: np.ndarray):\n",
    "        r, _ = X.shape\n",
    "        X = np.hstack([np.ones((r, 1)), X])\n",
    "        self.params = np.linalg.inv(X.T @ X) @ X.T @ y\n",
    "        return self\n",
    "\n",
    "    def predict(self, X: np.ndarray):\n",
    "        r, _ = X.shape\n",
    "        X = np.hstack([np.ones((r, 1)), X])\n",
    "        return X @ self.params\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "\n",
    "    regressor = RANSAC(model=LinearRegressor(), loss=square_error_loss, metric=mean_square_error)\n",
    "\n",
    "    X = np.array([-0.848,-0.800,-0.704,-0.632,-0.488,-0.472,-0.368,-0.336,-0.280,-0.200,-0.00800,-0.0840,0.0240,0.100,0.124,0.148,0.232,0.236,0.324,0.356,0.368,0.440,0.512,0.548,0.660,0.640,0.712,0.752,0.776,0.880,0.920,0.944,-0.108,-0.168,-0.720,-0.784,-0.224,-0.604,-0.740,-0.0440,0.388,-0.0200,0.752,0.416,-0.0800,-0.348,0.988,0.776,0.680,0.880,-0.816,-0.424,-0.932,0.272,-0.556,-0.568,-0.600,-0.716,-0.796,-0.880,-0.972,-0.916,0.816,0.892,0.956,0.980,0.988,0.992,0.00400]).reshape(-1,1)\n",
    "    y = np.array([-0.917,-0.833,-0.801,-0.665,-0.605,-0.545,-0.509,-0.433,-0.397,-0.281,-0.205,-0.169,-0.0531,-0.0651,0.0349,0.0829,0.0589,0.175,0.179,0.191,0.259,0.287,0.359,0.395,0.483,0.539,0.543,0.603,0.667,0.679,0.751,0.803,-0.265,-0.341,0.111,-0.113,0.547,0.791,0.551,0.347,0.975,0.943,-0.249,-0.769,-0.625,-0.861,-0.749,-0.945,-0.493,0.163,-0.469,0.0669,0.891,0.623,-0.609,-0.677,-0.721,-0.745,-0.885,-0.897,-0.969,-0.949,0.707,0.783,0.859,0.979,0.811,0.891,-0.137]).reshape(-1,1)\n",
    "\n",
    "    regressor.fit(X, y)\n",
    "\n",
    "    import matplotlib.pyplot as plt\n",
    "    plt.style.use(\"seaborn-darkgrid\")\n",
    "    fig, ax = plt.subplots(1, 1)\n",
    "    # 支持中文\n",
    "    # plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签\n",
    "    # plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号\n",
    "    print(regressor.best_fit.params)\n",
    "\n",
    "    plt.scatter(X, y)\n",
    "    plt.title(\"RANSAC拟合直线\")\n",
    "    line = np.linspace(-1, 1, num=100).reshape(-1, 1)\n",
    "    plt.plot(line, regressor.predict(line), c=\"peru\")\n",
    "    plt.show()\n"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "[[-0.12539864]\n",
      " [ 0.94634014]]\n"
     ]
    },
    {
     "output_type": "stream",
     "name": "stderr",
     "text": [
      "/home/st/ubuntu_data/software/miniconda3/envs/d2l-zh/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:240: RuntimeWarning: Glyph 25311 missing from current font.\n",
      "  font.set_text(s, 0.0, flags=flags)\n",
      "/home/st/ubuntu_data/software/miniconda3/envs/d2l-zh/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:240: RuntimeWarning: Glyph 21512 missing from current font.\n",
      "  font.set_text(s, 0.0, flags=flags)\n",
      "/home/st/ubuntu_data/software/miniconda3/envs/d2l-zh/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:240: RuntimeWarning: Glyph 30452 missing from current font.\n",
      "  font.set_text(s, 0.0, flags=flags)\n",
      "/home/st/ubuntu_data/software/miniconda3/envs/d2l-zh/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:240: RuntimeWarning: Glyph 32447 missing from current font.\n",
      "  font.set_text(s, 0.0, flags=flags)\n",
      "/home/st/ubuntu_data/software/miniconda3/envs/d2l-zh/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:203: RuntimeWarning: Glyph 25311 missing from current font.\n",
      "  font.set_text(s, 0, flags=flags)\n",
      "/home/st/ubuntu_data/software/miniconda3/envs/d2l-zh/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:203: RuntimeWarning: Glyph 21512 missing from current font.\n",
      "  font.set_text(s, 0, flags=flags)\n",
      "/home/st/ubuntu_data/software/miniconda3/envs/d2l-zh/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:203: RuntimeWarning: Glyph 30452 missing from current font.\n",
      "  font.set_text(s, 0, flags=flags)\n",
      "/home/st/ubuntu_data/software/miniconda3/envs/d2l-zh/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:203: RuntimeWarning: Glyph 32447 missing from current font.\n",
      "  font.set_text(s, 0, flags=flags)\n"
     ]
    },
    {
     "output_type": "display_data",
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {}
    }
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "orig_nbformat": 4,
  "language_info": {
   "name": "python",
   "version": "3.8.10",
   "mimetype": "text/x-python",
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "pygments_lexer": "ipython3",
   "nbconvert_exporter": "python",
   "file_extension": ".py"
  },
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3.8.10 64-bit ('d2l-zh': conda)"
  },
  "interpreter": {
   "hash": "0bedf3452ed48e38a2b75ff9f66ce8ae9ae2e92e8665cb618ee0797d2f46b9e5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}